/*
Copyright (c) 2005, MentorGen, LLC
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

+ Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer.
+ Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.
+ Neither the name of MentorGen LLC nor the names of its contributors may be
  used to endorse or promote products derived from this software without
  specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  POSSIBILITY OF SUCH DAMAGE.
 */
package com.mentorgen.tools.profile.runtime;

import java.util.HashMap;
import java.util.Map;

/**
 * A simple class to capture metrics for a method in a particular thread
 *
 * @author Andrew Wilcox
 *
 */
public final class Method {
	String _superClass;
	String _className;
	String _methodName;
	String _bundleName;
	String _loaderID;
	String _interfaces;
	
	Map<String, Long>   _inData = null;
	Map<String, Long>	_outData = null;
	Map<String, Long>	_inCount = null;
	Map<String, Long>   _outCount = null;
	
	Map<String, Method> _inMethod;
	Map<String, Method> _outMethod;

	Method(String loaderID, String bundleName, String className, String superClass, String interfaces, String methodName) {
		_loaderID = loaderID;
		_bundleName = bundleName;
		_className = className;
		_superClass = superClass;
		_interfaces = interfaces;
		_methodName = methodName;
		
		_inMethod = new HashMap<String, Method>();
		_outMethod = new HashMap<String, Method>();
		_inData = new HashMap<String, Long>();
		_outData = new HashMap<String, Long>();
		_inCount = new HashMap<String, Long>();
		_outCount = new HashMap<String, Long>();
	}
	
	Method (Method method){
		_loaderID = method._loaderID;
		_bundleName = method._bundleName;
		_className = method._className;
		_superClass = method._superClass;
		_interfaces = method._interfaces;
		_methodName = method._methodName;
		
		_inMethod = new HashMap<String, Method>();
		_outMethod = new HashMap<String, Method>();
		_inData = new HashMap<String, Long>();
		_outData = new HashMap<String, Long>();
		_inCount = new HashMap<String, Long>();
		_outCount = new HashMap<String, Long>();
	}

	public String getClassName() {
		return _className;
	}

	public String getMethodName() {
		return _methodName;
	}

	public String getBundleName(){
		if (_bundleName == null)
			return _loaderID;
		return _bundleName;
	}

	public String getSuperClassName(){
		return _superClass;
	}

	public String getInterfaces(){
		return _interfaces;
	}
	
	public Map<String, Method> getInMap(){
		return _inMethod;
	}
	
	public Map<String, Method> getOutMap(){
		return _outMethod;
	}
	
	public String getLoaderID(){
		return _loaderID;
	}

	String toInvertedString() {
		String className = this.getClassName().replace('/', '.');

		int index = className.lastIndexOf('.');
		String shortName = null;
		String packageName = "";

		if (index > -1) {
			shortName = className.substring(index+1);
			packageName = className.substring(0, index);
		} else {
			shortName = className;
		}

		StringBuffer b = new StringBuffer();
		b.append(shortName);
		b.append(':');
		b.append(this.getMethodName());
		b.append("\t(");
		b.append(packageName);
		b.append(":");
		b.append(this.getBundleName());
		b.append(")");
		return b.toString();
	}
	
	void addInData(String key, long inData){
		
		Long tmpData = _inData.get(key);
		if (tmpData == null)
			tmpData = new Long(0);
		tmpData += inData;
		_inData.put(key, tmpData);
		
		Long tmpCount = _inCount.get(key);
		if (tmpCount == null)
			tmpCount = new Long(0);
		tmpCount ++; 
		_inCount.put(key, tmpCount);
	}
	
	void addOutData(String key, long outData){
		Long tmpData = _outData.get(key);
		if (tmpData == null)
			tmpData = new Long(0);
		tmpData += outData;
		_outData.put(key, tmpData);
		
		Long tmpCount = _outCount.get(key);
		if (tmpCount == null)
			tmpCount = new Long(0);
		tmpCount ++; 
		_outCount.put(key, tmpCount);
	}
	
	public long getTotalInData(){
		Long total = new Long(0);
		for (String key : _inData.keySet())
			total += _inData.get(key);
		return total;
	}
	
	public long getTotalInDataForMethod(String methodKey){
		return _inData.get(methodKey);
	}
	
	public long getTotalOutData(){
		Long total = new Long(0);
		for (String key : _outData.keySet())
			total += _outData.get(key);
		return total;
	}
	
	public long getTotalOutDataForMethod(String methodKey){
		return _outData.get(methodKey);
	}
	
	public long getAvgInDataForMethod(String methodKey){
		return getTotalInDataForMethod (methodKey) / getInCountForMethod(methodKey);
	}
	
	public long getAvgOutDataForMethod(String methodKey){
		return getTotalOutDataForMethod(methodKey) / getOutCountForMethod(methodKey);
	}
	
	public long getTotalOutCount(){
		Long sum = new Long(0);
		for (String key : _outCount.keySet())
			sum += _outCount.get(key);
		return sum;
	}
	
	public long getOutCountForMethod(String methodKey){
		return _outCount.get(methodKey);
	}
	
	public long getTotalInCount(){
		Long sum = new Long(0);
		for (String key : _inCount.keySet())
			sum += _inCount.get(key);
		return sum;
	}
	
	public long getInCountForMethod(String methodKey){
		return _inCount.get(methodKey);
	}
	
	private Method checkMethodIntoMap(Map<String, Method> map, String methodKey){
		
		String lock = "lock";
		Method method = map.get(methodKey);
		
		synchronized(lock){
			if (method == null && EntityDictionary.getMethodDic().get(methodKey) != null){
				method = EntityDictionary.getMethodDic().get(methodKey);
				
				map.put(methodKey, method);
				
			}else if (method == null){
				String bundleLoader = EntityDictionary.getloaderClassDic().get(EntityDicHelper.getClassNameFromKey(methodKey));
				method = new Method(bundleLoader, EntityDictionary.getBundleDic().get(bundleLoader).getBundleSymbolicName(), 
						EntityDicHelper.getClassNameFromKey(methodKey),
						null, null, EntityDicHelper.getMethodNameFromKey(methodKey));
				
				EntityDictionary.getMethodDic().put(methodKey, method);
				map.put(methodKey, method);
			}
		}
		
		return method;
	}
	
	public void dataToMethod(String methodKey, long dataSize){
		
		Method method = checkMethodIntoMap(_outMethod, methodKey);
		
		method.checkMethodIntoMap(_inMethod, getThisMethodKey());

		this.addOutData(methodKey, dataSize);
		method.addInData(getThisMethodKey(), dataSize);
	}
	
	//
	// from object
	//

	public String toString() {
		StringBuffer b = new StringBuffer();
//		b.append(":");
		b.append(this.getClassName().replace('/', '.'));
		b.append(':');
		b.append(this.getMethodName());
		return b.toString();
	}

	@Override
	public boolean equals(Object other) {
		assert other instanceof Method;
		Method m = (Method) other;
		return this._className.equals(m._className)
			&& this._methodName.equals(m._methodName)
			&& this._loaderID.equals(m._loaderID)
			&& this._bundleName.equals(m._bundleName);
	}

	@Override
	public int hashCode() {
		return _loaderID.hashCode() + _bundleName.hashCode() +
			_className.hashCode() + _methodName.hashCode();
	}
	
	private String getThisMethodKey(){
		return EntityDicHelper.getMethodKey(EntityDicHelper.addClassExtension(_className), _methodName);
	}

}
